/**
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

package pittypat.web;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.Writer;

import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import pittypat.IJson;
import pittypat.Json;

/**
 * 表示由虚拟路径定义的文件。
 * <p>
 * 提供路径映射和通过虚拟路径删除文件的方法。
 */
public final class VirtualFile implements IJson {
	private static final Logger LOGGER = LogManager.getLogger(VirtualFile.class);
	// 当将文件发送到客户端时使用的读取和写入缓冲区的尺寸
	private static final int BUFFER_SIZE = 8192;

	/**
	 * 返回指定虚拟路径的物理路径。如果虚拟路径使用 "~/" 开头，则去掉前面的 '~'。
	 * 
	 * @param virtualPath
	 *            虚拟路径。
	 * @return 物理路径。
	 */
	public static String mapPath(String virtualPath) {
		if (virtualPath == null || virtualPath.length() == 0) {
			return "";
		}

		ServletContext sc = ApiContextListener.getCurrentContext();
		if (sc == null) {
			return null;
		}

		if (virtualPath.startsWith("~/")) {
			virtualPath = virtualPath.substring(1);
		}

		return sc.getRealPath(virtualPath);
	}

	/**
	 * 删除指定的虚拟路径上的文件。如果虚拟路径使用 "~/" 开头，则去掉前面的 '~'。
	 * 
	 * @param virtualPath
	 *            虚拟路径。
	 */
	public static void delete(String virtualPath) {
		try {
			String physicalPath = mapPath(virtualPath);
			if (physicalPath != null && physicalPath.length() > 0) {
				new File(physicalPath).delete();
			}
		} catch (Exception e) {
			LOGGER.error(e);
		}
	}

	// 虚拟路径
	private String virtualPath;
	// 文件名称和扩展名称
	private String fileName;
	// 文件内容类型
	private String contentType;
	// 物理路径
	private String physicalPath;

	/**
	 * 使用虚拟路径、文件名称和文件类型初始化 VirtualFile 的新实例。
	 */
	public VirtualFile(String virtualPath, String fileName, String contentType) {
		if (virtualPath == null || virtualPath.length() == 0) {
			throw new IllegalArgumentException("没有提供有效的文件虚拟路径。");
		}

		// 获取物理路径
		this.physicalPath = mapPath(virtualPath);
		// 获取文件名称
		this.fileName = fileName == null || fileName.length() == 0 ? Path.getFileName(this.physicalPath) : fileName;
		// 获取文件类型
		this.contentType = contentType == null || contentType.length() == 0 ? Mime.of(Path.getExtension(this.fileName))
				: contentType;
		this.virtualPath = virtualPath;
	}

	/**
	 * 使用虚拟路径、文件名称初始化 VirtualFile 的新实例。
	 */
	public VirtualFile(String virtualPath, String fileName) {
		this(virtualPath, fileName, null);
	}

	/**
	 * 使用虚拟路径初始化 VirtualFile 的新实例。
	 */
	public VirtualFile(String virtualPath) {
		this(virtualPath, null, null);
	}

	/**
	 * 将当前对象作为 JSON 字符串写入指定的文本流。
	 * 
	 * @param writer
	 *            目标文本流。
	 * @throws IllegalArgumentException
	 *             writer 是 null。
	 * @throws IOException
	 * @see pittypat.Model#write(java.io.Writer)
	 */
	@Override
	public void write(Writer writer) throws IOException {
		writer.write("{\"url\":\"" + Json.escape(this.virtualPath) + "\",\"name\":\"" + Json.escape(this.fileName)
				+ "\",\"type\":\"" + Json.escape(this.contentType) + "\"}");
	}

	/**
	 * 获取文件的虚拟路径。
	 * 
	 * @return 文件虚拟路径。
	 */
	public String getVirtualPath() {
		return this.virtualPath;
	}

	/**
	 * 获取文件的名称。
	 * 
	 * @return 文件名称。
	 */
	public String getFileName() {
		return this.fileName;
	}

	/**
	 * 获取文件的 MIME 类型。
	 * 
	 * @return MIME 类型。
	 */
	public String getContentType() {
		return this.contentType;
	}

	/**
	 * 获取文件的物理路径。
	 * 
	 * @return 文件物理路径。
	 */
	public String getPhysicalPath() {
		return this.physicalPath;
	}

	/**
	 * 同步通过指定的 HTTP 响应对象发送文件内容。
	 * 
	 * @param response
	 *            一个 HttpServletResponse 对象。如果为 null，则什么都不做。
	 * @throws IOException
	 */
	public void send(HttpServletResponse response) throws IOException {
		if (response == null) {
			return;
		}

		FileInputStream inStream = null;

		try {
			File file = new File(this.physicalPath);

			inStream = new FileInputStream(file);

			// 文件内容的长度
			response.setContentLengthLong(file.length());
			// 文件内容的类型
			response.setContentType(this.contentType);
			// 设置附件文件名称
			response.setHeader("Content-Disposition", "attachment; filename=" + this.fileName);

			// 响应输出流
			ServletOutputStream outStream = response.getOutputStream();

			// 读取和写入缓冲区
			byte[] buffer = new byte[BUFFER_SIZE];
			int bytesRead = -1;

			while ((bytesRead = inStream.read(buffer)) != -1) {
				outStream.write(buffer, 0, bytesRead);
			}
		} finally {
			if (inStream != null) {
				inStream.close();
			}
		}
	}
}
